#include <future>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

class Vehicle {
  public:
    Vehicle(int id) {
      _id = id;
    }

    int getID() { 
      return _id; 
    }

  private:
    int _id;
};

class WaitingVehicles {
  public:
    WaitingVehicles() {}

    Vehicle popBack() {
      // perform vector modification under the lock
      std::unique_lock<std::mutex> uLock(_mutex);
      _cond.wait(uLock, [this] {
        return !_vehicles.empty();
      });  // pass unique lock to condition variable

      // remove last vector element from queue
      Vehicle v = std::move(_vehicles.back());
      _vehicles.pop_back();

      return v;  // will not be copied due to return value optimization (RVO) in
                // C++
    }

    void pushBack(Vehicle &&v) {
      // simulate some work
      std::this_thread::sleep_for(std::chrono::milliseconds(100));

      // perform vector modification under the lock
      std::lock_guard<std::mutex> uLock(_mutex);

      // add vector to queue
      std::cout << "   Vehicle #" << v.getID() << " will be added to the queue"
                << std::endl;
      _vehicles.push_back(std::move(v));
      _cond.notify_one();  // notify client after pushing new Vehicle into vector
    }

  private:
    std::mutex _mutex;
    std::condition_variable _cond;
    std::vector<Vehicle>
        _vehicles;  // list of all vehicles waiting to enter this intersection
};

int main() {
  // create monitor object as a shared pointer to enable access by multiple
  // threads
  std::shared_ptr<WaitingVehicles> queue(new WaitingVehicles);

  std::cout << "Spawning threads..." << std::endl;
  std::vector<std::future<void>> futures;

  for (int i = 0; i < 10; ++i) {
    // create a new Vehicle instance and move it into the queue
    Vehicle v(i);
    futures.emplace_back(std::async(
        std::launch::async, &WaitingVehicles::pushBack, queue, std::move(v)));
  }

  std::cout << "Collecting results..." << std::endl;
  while (true) {
    // popBack wakes up when a new element is available in the queue
    Vehicle v = queue->popBack();
    std::cout << "   Vehicle #" << v.getID()
              << " has been removed from the queue" << std::endl;
  }

  // wait for the results
  for (std::future<void> &ftr : futures) {
    ftr.wait();
  }

  std::cout << "Finished!" << std::endl;

  return 0;
}